/*
 *  Copyright 2008 The MITRE Corporation (http://www.mitre.org/). All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.mitre.lattice.graph;

import java.util.ArrayList;

import org.jgap.Chromosome;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.impl.IntegerGene;
import org.mitre.lattice.lattice.LatticeFunctions;
import org.mitre.lattice.lattice.LatticeNode;

/**
 * Sample fitness function for the MakeChange example.
 */
public class MinDistanceFitness extends FitnessFunction
{
    public double maxDistance = 0;
    public static boolean isTest = true;
    public static int testValue = 0;

    public MinDistanceFitness(double maxDist)
    {
	    maxDistance = maxDist;
    }


     /* Determine the fitness of the given Chromosome instance. The higher the
     * return value, the more fit the instance. This method should always
     * return the same fitness value for two equivalent Chromosome instances.
     *
     * @param a_subject: The Chromosome instance to evaluate.
     *
     * @return A positive integer reflecting the fitness rating of the given
     *         Chromosome.
     */
    public int evaluate( Chromosome a_subject )
    {

	int distance = pullPointsTogether(a_subject);
	int fitness2 = pushPointsApart(a_subject);

	int fitness = (int)(maxDistance - distance) + fitness2 ;

	if (!isTest)
	   fitness = (fitness- ( testValue  )  )  ;

	//Don't know why - but the Reward Threshold - seems to improve the fit
	//This is the threshold which denotes a 'Good Fit'
	// If a 'Good Fit' is seen, improve the chances that this value will be selected.
	//Reward factor is usually about 1/2 the MaxDistance
	//if (fitness > (  maxDistance / 2) ) fitness = fitness * 2;

	if (fitness < 1) return 1;

	return fitness;

    }

    private int pullPointsTogether(Chromosome a_subject)
    {
	      // The fitness value measures both how close the value is to the
        // target amount
        // ------------------------------------------------------------------

        // Make sure fitness value is always positive.
        // -------------------------------------------

	/*Steps

	For each node
	1. Get the Gene associated with the node.
	2. Get all parents of node.

		For each parent node
		3. Get associated Gene, and gene value
		4. Evaluate distance between Gene (x only)
		5. Get levels of parent Node. Evaluate delta y
		6. Calculate distance

		7. Sum up distance over all parent nodes.

	8.Sum  up over all Nodes

	9. Get fitness value : maxDistance - sum of parents Dist.

	*/
	double distance = 0;

	for (int i=0; i < a_subject.getGenes().length; i++)
	{
		LatticeNode node = LatticeFunctions.getNode(new Integer(i));

		ArrayList parents = node.getParents();

		IntegerGene gene = (IntegerGene)a_subject.getGene(i);
		int x1 = gene.intValue();
		int levelChild = LatticeFunctions.getLevelOfNode(node);

		for (int j= 0; j < parents.size(); j++)
		{
			LatticeNode parentNode =(LatticeNode)parents.get(j);

			int geneNo = LatticeFunctions.getInteger( parentNode ).intValue();
			IntegerGene gene2 = (IntegerGene)a_subject.getGene(geneNo);
			int x2 = gene2.intValue();
			int levelParent = LatticeFunctions.getLevelOfNode(parentNode);

			int deltaY = (levelParent - levelChild) * LatticeFunctions.getBoxHeight();

			distance = distance + LatticeFunctions.getDistanceBetweenNodes(x1, x2, deltaY);

		}
	}

	return (int)distance;
    }

    public static void setNotTest()
    {
	    isTest = false;
    }

     public static void setTestValue(int test)
    {
	    testValue = test;
    }
     /**
     *
     * The points that are on the same level
     * need to be pushed apart so that they have a minimum distance
     * seperating them.
     * SO additional fitness points must be given
     */
    public static int pushPointsApart( Chromosome a_potentialSolution )
    {
       int fitness = 0;
       int numberOfGenes = a_potentialSolution.size();
       Gene[] allGenes = a_potentialSolution.getGenes();

       for( int i = 0; i < numberOfGenes; i++ )
       {
	       LatticeNode node = LatticeFunctions.getNode(new Integer(i));

	       ArrayList nodesOnThisLevel = LatticeFunctions.getNodesOnSameLevel(node);

//	       int weight = 0;

	       //Cycle through all nodes on the same level
	       for (int j = 0; j < nodesOnThisLevel.size(); j++)
	       {
		       int geneIndex = ((Integer)nodesOnThisLevel.get(j)).intValue();

		       //Make sure that this node is not being compared
		       //to itself
		       if (! (geneIndex == i) )
		       {
			       int minDist = ((IntegerGene)allGenes[i]).intValue() - ((IntegerGene)allGenes[geneIndex]).intValue();

			       //IF the distance between the two is greater than boxWIdth
			       //add a weighting factor

			       if (minDist > (0.5 * LatticeFunctions.getBoxWidth()) )
				        fitness = fitness + LatticeFunctions.getBoxHeight() *2 ;
		       }
	       }

       }
       return fitness;
    }

    /**
     * Retrieves the distance represented by the given potential
     * solution at the given gene position.
     *
     * @param a_potentialSolution The potential solution to evaluate.
     * @param a_position The gene position to evaluate.
     * @return the number of coins represented by the potential solution
     *         at the given gene position.
     */
    public static double getDistanceAtGene( Chromosome a_potentialSolution,
                                        int a_position )
    {
        Integer xCoord =
          (Integer) a_potentialSolution.getGene( a_position ).getAllele();

        return xCoord.doubleValue();
    }

    /**
     * Returns the total distance represented by all of the genes in
     * the given potential solution.
     *
     * @param a_potentialsolution The potential solution to evaluate.
     * @return The total distance represented by the given Chromosome.
     */
    public static int getTotalDistance( Chromosome a_potentialsolution )
    {
        int totalDistance = 0;

        int numberOfGenes = a_potentialsolution.size();
        for( int i = 0; i < numberOfGenes; i++ )
        {
            totalDistance += getDistanceAtGene( a_potentialsolution, i );
        }

        return totalDistance;
    }
}


